﻿using System;
using System.Collections.Generic;
using System.Text;
using Tesla.Collections;
using Tesla.Content;
using Tesla.Math;

namespace Tesla.Graphics {
    /// <summary>
    /// SpriteFont represents a font texture where each character corresponds
    /// to a portion of the texture and includes spacing data such as kernings to
    /// properly render string data.
    /// </summary>
    public sealed class SpriteFont : ISavable {
        private ReadOnlyDictionary<char, Glyph> _readonlyGlyphs;
        private Dictionary<char, Glyph> _glyphs;
        private Dictionary<char, Dictionary<char, int>> _kerningMap;
        private int _lineHeight;
        private Texture2D _texture;
        private char? _defaultChar;

        /// <summary>
        /// Gets the default character, used if an invalid character
        /// is read from a string to render.
        /// </summary>
        public char? DefaultCharacter {
            get {
                return _defaultChar;
            }
        }

        /// <summary>
        /// Gets the spacing between consecutive lines, if
        /// the string has a '\n' new line break.
        /// </summary>
        public int LineHeight {
            get {
                return _lineHeight;
            }
        }

        /// <summary>
        /// Gets the font texture.
        /// </summary>
        public Texture2D Texture {
            get {
                return _texture;
            }
        }

        /// <summary>
        /// Gets a read only dictionary of the characters and their
        /// corresponding glyph data.
        /// </summary>
        public ReadOnlyDictionary<char, Glyph> Characters {
            get {
                if(_readonlyGlyphs == null) {
                    _readonlyGlyphs = new ReadOnlyDictionary<char, Glyph>(_glyphs);
                }

                return _readonlyGlyphs;
            }
        }

        /// <summary>
        /// Creates a new instance of <see cref="SpriteFont"/>.
        /// </summary>
        /// <param name="texture">The font texture.</param>
        /// <param name="lineHeight">Line space between consecutive lines.</param>
        public SpriteFont(Texture2D texture, int lineHeight) {
            _glyphs = new Dictionary<char, Glyph>();
            _kerningMap = new Dictionary<char, Dictionary<char, int>>();
            _defaultChar = null;
            _lineHeight = lineHeight;
            _texture = texture;
        }

        /// <summary>
        /// Creates a new instance of <see cref="SpriteFont"/>.
        /// </summary>
        /// <param name="texture">The font texture.</param>
        /// <param name="lineHeight">Line space between consecutive lines.</param>
        /// <param name="defaultChar">The default character.</param>
        public SpriteFont(Texture2D texture, int lineHeight, Glyph defaultChar) {
            _glyphs = new Dictionary<char, Glyph>();
            _kerningMap = new Dictionary<char, Dictionary<char, int>>();
            _defaultChar = defaultChar.Literal;
            _lineHeight = lineHeight;
            _texture = texture;
            _glyphs.Add(defaultChar.Literal, defaultChar);
        }

        /// <summary>
        /// Adds a glyph to the character map. 
        /// </summary>
        /// <param name="character">Glyph to add</param>
        public void AddGlyph(Glyph character) {
            _glyphs.Add(character.Literal, character);
        }

        /// <summary>
        /// Adds a kerning pair to the sprite font. Kerning is
        /// the process of adjusting a character's horizontal space
        /// with the preceeding character.
        /// </summary>
        /// <param name="second">Second character</param>
        /// <param name="first">First character preceeding</param>
        /// <param name="amount">Amount to move the second character</param>
        public void AddKerning(char second, char first, int amount) {
            Dictionary<char, int> dict;
            if(!_kerningMap.TryGetValue(second, out dict)){
                dict = new Dictionary<char,int>();
                _kerningMap.Add(second, dict);
            }
            dict.Add(first, amount);
        }

        /// <summary>
        /// Get the kerning adjustment for the character pair.
        /// </summary>
        /// <param name="second">Second character</param>
        /// <param name="first">First character preceeding</param>
        /// <returns>Amount to move the second character</returns>
        public int GetKerning(char second, char first) {
            Dictionary<char, int> dict;
            if(_kerningMap.TryGetValue(second, out dict)) {
                int amt;
                if(dict.TryGetValue(first, out amt)) {
                    return amt;
                }
            }
            return 0;
        }

        /// <summary>
        /// Measures the string, returning the maximum width and height
        /// (stored in the X, Y coordinates respectively) of the string
        /// as it would be if rendered.
        /// </summary>
        /// <param name="text">The string</param>
        /// <returns>Width, height of the string</returns>
        public Vector2 MeasureString(String text) {
            StringReference strRef = new StringReference(text);
            return MeasureString(ref strRef);
        }

        /// <summary>
        /// Measures the string, returning the maximum width and height
        /// (stored in the X, Y coordinates respectively) of the string
        /// as it would be if rendered.
        /// </summary>
        /// <param name="text">The string builder</param>
        /// <returns>Width, height of the string</returns>
        public Vector2 MeasureString(StringBuilder text) {
            StringReference strRef = new StringReference(text);
            return MeasureString(ref strRef);
        }

        private Vector2 MeasureString(ref StringReference str) {
            if(str.Length == 0) {
                return Vector2.Zero;
            }

            Vector2 dim = Vector2.Zero;
            dim.Y = LineHeight;
            float lx = 0;
            float ly = 0;
            int num = 0;
            char prevChar = '\0';

            for(int i = 0; i < str.Length; i++) {
                char literal = str[i];
                switch(literal) {
                    case '\r':
                        break;
                    case '\n':
                        lx = 0;
                       // ly += LineHeight;
                      //  dim.Y = System.Math.Max(ly, dim.Y);
                        num++;
                        break;
                    default:
                        Glyph glyph;
                        if(!_glyphs.TryGetValue(literal, out glyph)) {
                            if(_defaultChar.HasValue) {
                                glyph = _glyphs[_defaultChar.Value];
                            }
                        }
                        lx += Advance(ref glyph, ref prevChar);
                        prevChar = literal;
                        dim.X = System.Math.Max(lx, dim.X);
                        dim.Y = System.Math.Max(ly + glyph.Offsets.Y, dim.Y);
                        break;
                }
            }
            dim.Y += num * LineHeight;
            return dim;
        }

        private int Advance(ref Glyph g, ref char prevChar) {
            return (int) g.Offsets.Z + GetKerning(g.Literal, prevChar);
        }

        internal void DrawString(SpriteBatch batch, StringReference text, Vector2 position, Color tintColor, float angle, Vector2 origin, Vector2 scale, SpriteFlipEffect flipEffect, float depth) {
            Matrix transform;
            Matrix translation;
            Matrix.FromRotationZ(angle, out transform);
            Matrix.FromTranslation(-origin.X * scale.X, -origin.Y * scale.Y, 0.0f, out translation);
            Matrix.Multiply(ref translation, ref transform, out transform);
            
            float lx = position.X;
            float ly = position.Y;

            char prevChar = '\0';
            bool newLine = false;
            for(int i = 0; i < text.Length; i++) {
                char literal = text[i];
                switch(literal) {
                    case '\r':
                        break;
                    case '\n':
                        lx = position.X;
                        ly += LineHeight;
                        newLine = true;
                        break;
                    default: 
                        Glyph glyph;
                        if(!_glyphs.TryGetValue(literal, out glyph)) {
                            if(_defaultChar.HasValue) {
                                glyph = _glyphs[_defaultChar.Value];
                            }
                        }

                        Vector2 pos = new Vector2(lx + glyph.Offsets.X, ly + glyph.Offsets.Y);
                        Vector2.Transform(ref pos, ref translation, out pos);

                        //batch.Draw(_texture, pos, glyph.Cropping, Color.White);
                        batch.Draw(_texture, pos, scale, glyph.Cropping, origin, tintColor, flipEffect, angle, depth);

                        lx += Advance(ref glyph, ref prevChar);
                        prevChar = literal;
                        newLine = false;
                        break;
                }
            }
        }

        internal struct StringReference {
            private String _string;
            private StringBuilder _stringB;

            public char this[int index] {
                get {
                    if(_string != null) {
                        return _string[index];
                    } else if(_stringB != null) {
                        return _stringB[index];
                    }
                    return '\0';
                }
            }

            public int Length {
                get {
                    if(_string != null) {
                        return _string.Length;
                    } else if(_stringB != null) {
                        return _stringB.Length;
                    }
                    return 0;
                }
            }

            public StringReference(String str) {
                _string = str;
                _stringB = null;
            }

            public StringReference(StringBuilder strB) {
                _string = null;
                _stringB = strB;
            }
        }

        /// <summary>
        /// Serializes the object and writes it to the output.
        /// </summary>
        /// <param name="output">Savable Output</param>
        public void Write(ISavableWriter output) {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Deserializes the object and populates it from the input.
        /// </summary>
        /// <param name="input">Savable input</param>
        public void Read(ISavableReader input) {
            throw new NotImplementedException();
        }
    }

    /// <summary>
    /// Represents a glyph on a texture.
    /// </summary>
    public struct Glyph {
        private char _literal;
        private Rectangle _cropping;
        private Vector3 _offsets;

        /// <summary>
        /// Character the glyph corresponds to.
        /// </summary>
        public char Literal {
            get {
                return _literal;
            }
        }

        /// <summary>
        /// Cropping rectangle, scaled by the
        /// sprite's texture width/height to produce
        /// the UV coordinates of the sprite.
        /// </summary>
        public Rectangle Cropping {
            get {
                return _cropping;
            }
        }

        /// <summary>
        /// Screen offsets representing how the glyph is aligned.
        /// X,Y coordinates are the top-left origin of the quad
        /// on the screen. The Z coordinate is the amount of space
        /// to add from the top-left corner to the next gylph's top-left
        /// corner along the X-Axis.
        /// </summary>
        public Vector3 Offsets {
            get {
                return _offsets;
            }
        }

        /// <summary>
        /// Creates a new instance of <see cref="Glyph"/>.
        /// </summary>
        /// <param name="literal">The character literal.</param>
        /// <param name="cropping">The cropping rectangle, in pixels</param>
        /// <param name="offsets">The screen offsets (x,y correspond to top-left corner and z is the amount to space after the glyph)</param>
        public Glyph(char literal, Rectangle cropping, Vector3 offsets) {
            _literal = literal;
            _cropping = cropping;
            _offsets = offsets;
        }
    }
}
